package com.ebarlas.fractal;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.List;

import com.ebarlas.fractal.compile.CompiledExpression;
import com.ebarlas.fractal.compile.CompilerImpl;
import com.ebarlas.fractal.compile.Compiler;
import com.ebarlas.fractal.compile.ParseException;
import com.ebarlas.fractal.compile.Parser;
import com.ebarlas.fractal.compile.ParserImpl;
import com.ebarlas.fractal.compile.ScanException;
import com.ebarlas.fractal.compile.StackCommand;
import com.ebarlas.fractal.compile.Token;
import com.ebarlas.fractal.compile.Variable;
import com.ebarlas.fractal.util.Complex;

public class CompilerFractalService implements FractalService {
    
    public BufferedImage createFractalImage(Fractal fractal, int imgWidth, int imgHeight) throws ScanException, ParseException {
        return createFractalImage(compile(fractal), imgWidth, imgHeight);
    }
    
    @Override
    public void createFractalImages(FractalTransition ft, TransitionListener listener, int frames, int imgWidth, int imgHeight) throws ScanException, ParseException {
        createFractalImages(ft, compile(ft), listener, frames, imgWidth, imgHeight);
    }
    
    private static CompiledFractalTransition compile(FractalTransition ft) throws ScanException, ParseException {
        Variable frameVar = new FieldAccessVariable(TransitionRegionCompiledExpression.FRAME_VAR_NAME);
        Variable framesVar = new FieldAccessVariable(TransitionRegionCompiledExpression.FRAMES_VAR_NAME);        
        Variable pointVar = new FieldAccessVariable(TransitionBaseCompiledExpression.POINT_VAR_NAME);
        Variable iterationVar = new FieldAccessVariable(TransitionRecurrenceCompiledExpression.ITERATION_VAR_NAME);
        Variable recurrenceVar = new FieldAccessVariable(TransitionRecurrenceCompiledExpression.RECURRENCE_VAR_NAME);
        
        Parser parser = new ParserImpl();  
        
        parser.addVariable("f|F", frameVar);
        parser.addVariable("t|t", framesVar); 
        
        TransitionRegionCompiledExpression regionX = compile(parser, ft.getRegionX(), TransitionRegionCompiledExpression.class);
        TransitionRegionCompiledExpression regionY = compile(parser, ft.getRegionY(), TransitionRegionCompiledExpression.class);
        TransitionRegionCompiledExpression regionWidth = compile(parser, ft.getRegionWidth(), TransitionRegionCompiledExpression.class);
        TransitionRegionCompiledExpression regionHeight = compile(parser, ft.getRegionHeight(), TransitionRegionCompiledExpression.class);
        
        parser.addVariable("c|C", pointVar); 
        
        TransitionBaseCompiledExpression base = compile(parser, ft.getBase(), TransitionBaseCompiledExpression.class);
        
        parser.addVariable("n|N", iterationVar);
        parser.addVariable("z|Z", recurrenceVar);
        
        TransitionRecurrenceCompiledExpression recurrence = compile(parser, ft.getRecurrence(), TransitionRecurrenceCompiledExpression.class);
        TransitionRecurrenceCompiledExpression red = compile(parser, ft.getRed(), TransitionRecurrenceCompiledExpression.class);
        TransitionRecurrenceCompiledExpression green = compile(parser, ft.getGreen(), TransitionRecurrenceCompiledExpression.class);
        TransitionRecurrenceCompiledExpression blue = compile(parser, ft.getBlue(), TransitionRecurrenceCompiledExpression.class);
        TransitionRecurrenceCompiledExpression halt = compile(parser, ft.getHalt(), TransitionRecurrenceCompiledExpression.class);        
        
        CompiledFractalTransition cft = new CompiledFractalTransition();
        cft.setBase(base);
        cft.setRecurrence(recurrence);
        cft.setRed(red);
        cft.setGreen(green);
        cft.setBlue(blue);
        cft.setHalt(halt);
        cft.setRegionX(regionX);
        cft.setRegionY(regionY);
        cft.setRegionWidth(regionWidth);
        cft.setRegionHeight(regionHeight);
        
        return cft;
    }    

    private static CompiledFractal compile(Fractal fractal) throws ScanException, ParseException {
        Variable pointVar = new FieldAccessVariable(BaseCompiledExpression.POINT_VAR_NAME);
        Variable iterationVar = new FieldAccessVariable(RecurrenceCompiledExpression.ITERATION_VAR_NAME);
        Variable recurrenceVar = new FieldAccessVariable(RecurrenceCompiledExpression.RECURRENCE_VAR_NAME);
        
        Parser parser = new ParserImpl();  
        parser.addVariable("c|C", pointVar); 
        
        BaseCompiledExpression base = compile(parser, fractal.getBase(), BaseCompiledExpression.class);
        
        parser.addVariable("n|N", iterationVar);
        parser.addVariable("z|Z", recurrenceVar);
        
        RecurrenceCompiledExpression recurrence = compile(parser, fractal.getRecurrence(), RecurrenceCompiledExpression.class);
        RecurrenceCompiledExpression red = compile(parser, fractal.getRed(), RecurrenceCompiledExpression.class);
        RecurrenceCompiledExpression green = compile(parser, fractal.getGreen(), RecurrenceCompiledExpression.class);
        RecurrenceCompiledExpression blue = compile(parser, fractal.getBlue(), RecurrenceCompiledExpression.class);
        RecurrenceCompiledExpression halt = compile(parser, fractal.getHalt(), RecurrenceCompiledExpression.class);        
        
        CompiledFractal cf = new CompiledFractal();
        cf.setBase(base);
        cf.setRecurrence(recurrence);
        cf.setRed(red);
        cf.setGreen(green);
        cf.setBlue(blue);
        cf.setHalt(halt);
        cf.setRegionX(fractal.getRegionX());
        cf.setRegionY(fractal.getRegionY());
        cf.setRegionWidth(fractal.getRegionWidth());
        cf.setRegionHeight(fractal.getRegionHeight());
        
        return cf;
    }   
    
    private static <T extends CompiledExpression> T compile(Parser parser, String expr, Class<T> parent) throws ScanException, ParseException {
        List<Token> tokens = parser.scan(expr);
        List<StackCommand> commands = parser.parse(tokens);
        Compiler compiler = new CompilerImpl();
        return compiler.compile(parent, commands);
    }
    
    private static Color computeCurrentPoint(CompiledFractal cf) {   
        // execute base
        Complex recurrence = cf.getBase().execute();
        cf.getRecurrence().setRecurrence(recurrence);
        
        cf.getHalt().setIteration(Complex.ZERO);
        cf.getHalt().setRecurrence(recurrence);
        
        // iterate while not halted by halt expression
        int i=0;
        while(!cf.getHalt().execute().getBoolean()) {
            // update iteration
            i++;
            Complex iters = new Complex(i);
            cf.getHalt().setIteration(iters);
            cf.getRecurrence().setIteration(iters);
            
            // update recurrence
            recurrence = cf.getRecurrence().execute();
            cf.getHalt().setRecurrence(recurrence);
            cf.getRecurrence().setRecurrence(recurrence);            
        }    
        
        Complex iters = new Complex(i);
        
        // set final iteration and recurrence values on color exprs
        cf.getRed().setIteration(iters);
        cf.getRed().setRecurrence(recurrence);
        cf.getGreen().setIteration(iters);
        cf.getGreen().setRecurrence(recurrence);
        cf.getBlue().setIteration(iters);
        cf.getBlue().setRecurrence(recurrence);        

        // compute rgb
        int r = Math.abs(cf.getRed().execute().getInt()) % 256;
        int g = Math.abs(cf.getGreen().execute().getInt()) % 256;
        int b = Math.abs(cf.getBlue().execute().getInt()) % 256;

        // return result  
        return new Color(r, g, b);        
    }   
    
    private static Color computeCurrentPoint(CompiledFractalTransition cft) {     
        // execute base
        Complex recurrence = cft.getBase().execute();
        cft.getRecurrence().setRecurrence(recurrence);
        
        cft.getHalt().setIteration(Complex.ZERO);
        cft.getHalt().setRecurrence(recurrence);
        
        // iterate while not halted by halt expression
        int i=0;
        while(!cft.getHalt().execute().getBoolean()) {
            // update iteration
            i++;
            Complex iters = new Complex(i);
            cft.getHalt().setIteration(iters);
            cft.getRecurrence().setIteration(iters);
            
            // update recurrence
            recurrence = cft.getRecurrence().execute();
            cft.getHalt().setRecurrence(recurrence);
            cft.getRecurrence().setRecurrence(recurrence);            
        }    
        
        Complex iters = new Complex(i);
        
        // set final iteration and recurrence values on color exprs
        cft.getRed().setIteration(iters);
        cft.getRed().setRecurrence(recurrence);
        cft.getGreen().setIteration(iters);
        cft.getGreen().setRecurrence(recurrence);
        cft.getBlue().setIteration(iters);
        cft.getBlue().setRecurrence(recurrence);        

        // compute rgb
        int r = Math.abs(cft.getRed().execute().getInt()) % 256;
        int g = Math.abs(cft.getGreen().execute().getInt()) % 256;
        int b = Math.abs(cft.getBlue().execute().getInt()) % 256;

        // return result  
        return new Color(r, g, b);        
    }    
    
    private static BufferedImage createFractalImage(CompiledFractal cf, int imgWidth, int imgHeight) {
        BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();        
        
        double reLower = cf.getRegionX();
        double imLower = cf.getRegionY();
        double reInterval = cf.getRegionWidth() / imgWidth;
        double imInterval = cf.getRegionHeight() / imgHeight;

        for(int x=0; x<imgWidth; x++) {
            for(int y=0; y<imgHeight; y++) {
                // set point on all compiled expressions
                Complex point = new Complex(reLower + x * reInterval, imLower + y * imInterval);
                cf.getBase().setPoint(point);
                cf.getRecurrence().setPoint(point);
                cf.getRed().setPoint(point);
                cf.getGreen().setPoint(point);
                cf.getBlue().setPoint(point);
                cf.getHalt().setPoint(point); 
                
                g.setColor(computeCurrentPoint(cf));
                g.fillRect(x, y, 1, 1);
            }
        }
        
        return image;
    }    
    
    private static void createFractalImages(FractalTransition ft, CompiledFractalTransition cft, TransitionListener listener, int frames, int imgWidth, int imgHeight) {             
        // set frames for all compiled expressions
        Complex t = new Complex(frames);
        cft.getBase().setFrames(t);
        cft.getRecurrence().setFrames(t);
        cft.getRed().setFrames(t);
        cft.getGreen().setFrames(t);
        cft.getBlue().setFrames(t);
        cft.getHalt().setFrames(t);
        cft.getRegionX().setFrames(t);
        cft.getRegionY().setFrames(t);
        cft.getRegionWidth().setFrames(t);
        cft.getRegionHeight().setFrames(t);
        
        for(int i=0; i<frames; i++) {
            BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics g = image.getGraphics();
            
            // set frame for all compiled expressions
            Complex f = new Complex(i);
            cft.getBase().setFrame(f);
            cft.getRecurrence().setFrame(f);
            cft.getRed().setFrame(f);
            cft.getGreen().setFrame(f);
            cft.getBlue().setFrame(f);
            cft.getHalt().setFrame(f);
            cft.getRegionX().setFrame(f);
            cft.getRegionY().setFrame(f);
            cft.getRegionWidth().setFrame(f);
            cft.getRegionHeight().setFrame(f);            
            
            double reLower = cft.getRegionX().execute().getReal();
            double imLower = cft.getRegionY().execute().getReal();
            double reInterval = cft.getRegionWidth().execute().getReal() / imgWidth;
            double imInterval = cft.getRegionHeight().execute().getReal() / imgHeight;
            
            for(int x=0; x<imgWidth; x++) {
                for(int y=0; y<imgHeight; y++) {
                    // set point on non-region compiled expressions
                    Complex point = new Complex(reLower + x * reInterval, imLower + y * imInterval);
                    cft.getBase().setPoint(point);
                    cft.getRecurrence().setPoint(point);
                    cft.getRed().setPoint(point);
                    cft.getGreen().setPoint(point);
                    cft.getBlue().setPoint(point);
                    cft.getHalt().setPoint(point);                     
                    
                    g.setColor(computeCurrentPoint(cft));
                    g.fillRect(x, y, 1, 1);
                }
            }
            
            listener.transition(ft, image, i, frames);
        }
        
    }    
}
